package cc.rengu.oltp.service.realize.impl;

import cc.rengu.oltp.service.common.constant.AppParamConstant;
import cc.rengu.oltp.service.common.dao.SequenceMapper;
import cc.rengu.oltp.service.common.dao.SmsInfoMapper;
import cc.rengu.oltp.service.common.dao.SysParamMapper;
import cc.rengu.oltp.service.common.dao.impl.SequenceMapperImpl;
import cc.rengu.oltp.service.common.dao.impl.SmsInfoMapperImpl;
import cc.rengu.oltp.service.common.dao.impl.SysParamMapperImpl;
import cc.rengu.oltp.service.common.entity.SmsInfo;
import cc.rengu.oltp.service.common.entity.SysParam;
import cc.rengu.oltp.service.common.enums.OltpRpcdEnum;
import cc.rengu.oltp.service.model.BizException;
import cc.rengu.oltp.service.realize.SmsService;
import cc.rengu.oltp.utility.util.DateUtil;
import cc.rengu.oltp.utility.util.RandomUtil;
import cc.rengu.oltp.utility.util.RedisUtil;
import cc.rengu.utility.log.RgLog;
import cc.rengu.utility.log.RgLogger;
import com.alibaba.fastjson.JSON;

import java.util.List;
import java.util.Optional;

/**
 * 短信验证码服务
 * Created by 王成 on 2020/2/7
 */
public class SmsServiceImpl implements SmsService {
    private final RgLogger rglog = RgLog.getLogger(this.getClass().getName());
    private final static String SMS_APPLY_LIMIT_PARAM = "SMS_APPLY_LIMIT_PARAM";
    private final static String SMS_VERIFY_PARAM = "SMS_VERIFY_PARAM";
    private final static int SMS_MAX_VERIFY_TIMES = 3;  /* 最大尝试验证次数 */
    private final static long SMS_MAX_EFFECTIVE_TIME = 300L;  /* 5分钟 */

    @Override
    public SmsInfo generateSmsCode(String instId, String mobileNo, String smsType) throws Exception {
        SequenceMapper sequenceMapper = new SequenceMapperImpl();
        String sequenceNo = sequenceMapper.getNextSmsVerifySeqNo(8);
        if (null == sequenceNo) {
            rglog.error("生成短信验证码验证序列号失败!");
            throw new BizException(OltpRpcdEnum.GENERATE_SMS_CODE_ERROR);
        }
        String verifySeqNum = instId + DateUtil.getCurrentDateTime("yyyyMMdd") + sequenceNo;
        return generateSmsCode(instId, mobileNo, smsType, AppParamConstant.SYS_ID, verifySeqNum);
    }

    @Override
    public SmsInfo generateSmsCode(String instId, String mobileNo, String smsType, String transChannelId, String verifySeqNum) throws Exception {
        /* Step1：获取短信验证码申请次数限制参数 */
        SmsInfoMapper smsInfoMapper = new SmsInfoMapperImpl();
        String smsApplyLimitTimesKey = instId.trim() + SMS_APPLY_LIMIT_PARAM;
        List<SysParam> sysParamList = JSON.parseArray(RedisUtil.hashGet(AppParamConstant.SYS_PARAM_CACHE, smsApplyLimitTimesKey), SysParam.class);
        if (null == sysParamList || sysParamList.isEmpty()) {
            SysParamMapper sysParamMapper = new SysParamMapperImpl();
            sysParamList = sysParamMapper.selectSysParamByInstIdAndParamType(instId.trim(), SMS_APPLY_LIMIT_PARAM);
        }
        if (null != sysParamList && !sysParamList.isEmpty()) {
            /* Step1.1：检查短信申请是否超限定次数 */
            for (SysParam sysParam : sysParamList) {
                int smsApplyLimitTimes = Integer.parseInt(sysParam.getParamValue().trim());
                String lastApplyTime = DateUtil.getPrevioustimes(Integer.parseInt(sysParam.getParamValue().trim()) * 60 * 1000L, "yyyy-MM-dd HH:mm:ss.SSS");
                rglog.debug("短信申请统计开始时间:<{}>", lastApplyTime);
                List<SmsInfo> smsInfos = smsInfoMapper.selectSmsIfoByMobileNo(instId, mobileNo, lastApplyTime);
                if (null != smsInfos && !smsInfos.isEmpty() && smsInfos.size() > smsApplyLimitTimes) {
                    rglog.error("手机号<{}>最近{}已超过限制次数:<{}>", mobileNo, sysParam.getParamDesc(), smsApplyLimitTimes);
                    throw new BizException(OltpRpcdEnum.SMS_APPLY_OVER_LIMIT);
                }
            }
        }
        /* Step2：随机生成短信验证码（开发环境默认生成6个1） */
        String smsCode = RandomUtil.getRandomInt();
        if (AppParamConstant.DEFAULT_ENV.equals(AppParamConstant.APPLICATION_ENV)) {
            smsCode = AppParamConstant.DEFAULT_SMS_CODE;
        }
        /* Step3：登记短信验证码信息 */
        SmsInfo smsInfo = new SmsInfo();
        smsInfo.setInstId(instId);
        smsInfo.setSmsType(smsType);
        smsInfo.setSmsCode(smsCode);
        smsInfo.setVerifyMobileNo(mobileNo);
        smsInfo.setVerifySeqNum(transChannelId + verifySeqNum);
        long currentTimeStamp = System.currentTimeMillis();
        smsInfo.setSmsCreateTime(DateUtil.getDateByTimeStamp(currentTimeStamp, "yyyy-MM-dd HH:mm:ss.SSS"));
        String smsVerifyParamKey = instId.trim() + SMS_VERIFY_PARAM;
        List<SysParam> sysVerifyParamList = JSON.parseArray(RedisUtil.hashGet(AppParamConstant.SYS_PARAM_CACHE, smsVerifyParamKey), SysParam.class);
        if (null == sysVerifyParamList || sysVerifyParamList.isEmpty()) {
            SysParamMapper sysParamMapper = new SysParamMapperImpl();
            sysVerifyParamList = sysParamMapper.selectSysParamByInstIdAndParamType(instId.trim(), SMS_VERIFY_PARAM);
        }
        if (null != sysVerifyParamList && !sysVerifyParamList.isEmpty()) {
            Optional<SysParam> sysParamOpt = sysVerifyParamList.stream().filter(item -> "SMS_MAX_VERIFY_TIMES".equals(item.getParamKey().trim())).findFirst();
            if (sysParamOpt.isPresent()) {
                smsInfo.setTryMaxTimes(Integer.parseInt(sysParamOpt.get().getParamValue().trim()));
            } else {
                smsInfo.setTryMaxTimes(SMS_MAX_VERIFY_TIMES);
            }
            sysParamOpt = sysVerifyParamList.stream().filter(item -> "SMS_MAX_EFFECTIVE_TIME".equals(item.getParamKey().trim())).findFirst();
            if (sysParamOpt.isPresent()) {
                String smsExpiryTime = DateUtil.getDateByTimeStamp(currentTimeStamp + Integer.parseInt(sysParamOpt.get().getParamValue().trim()) * 1000L, "yyyy-MM-dd HH:mm:ss.SSS");
                smsInfo.setSmsExpiryTime(smsExpiryTime);
            } else {
                smsInfo.setSmsExpiryTime(DateUtil.getDateByTimeStamp(currentTimeStamp + SMS_MAX_EFFECTIVE_TIME * 1000, "yyyy-MM-dd HH:mm:ss.SSS"));
            }
        } else {
            smsInfo.setSmsExpiryTime(DateUtil.getDateByTimeStamp(currentTimeStamp + SMS_MAX_EFFECTIVE_TIME * 1000, "yyyy-MM-dd HH:mm:ss.SSS"));
            smsInfo.setTryMaxTimes(SMS_MAX_VERIFY_TIMES);
        }
        smsInfo.setTryTimes(0);
        smsInfo.setSmsStatus(AppParamConstant.NO);
        int iReturnCode = smsInfoMapper.insertSmsInfo(smsInfo);
        if (0 != iReturnCode) {
            rglog.error("生成并登记短信验证码信息失败,iReturnCode:<{}>", iReturnCode);
            return null;
        }
        return smsInfo;
    }

    @Override
    public boolean verifySmsCode(String instId, String smsType, String smsCode, String verifySeqNum, String... verifyPhoneNo) throws Exception {
        return verifySmsCodeCore(instId, smsType, smsCode, AppParamConstant.SYS_ID, verifySeqNum, verifyPhoneNo);
    }


    @Override
    public boolean verifySmsCodeWithChannel(String instId, String smsType, String smsCode, String transChannelId, String verifySeqNum, String... verifyPhoneNo) throws Exception {
        if (AppParamConstant.SYS_ID.equals(transChannelId)) {
            //该方法只支持非IGAS渠道验证
            rglog.error("该短信验证码只支持外部生成短信验证码渠道(非IGAS)校验,当前渠道为:[{}]", transChannelId);
            throw new BizException(OltpRpcdEnum.VERIFY_SMS_CODE_NOT_SUPPORT_IGAS_ERROR);
        }
        return verifySmsCodeCore(instId, smsType, smsCode, transChannelId, verifySeqNum, verifyPhoneNo);
    }

    private boolean verifySmsCodeCore(String instId, String smsType, String smsCode, String transChannelId, String verifySeqNum, String... verifyPhoneNo) throws Exception {
        /* Step1：获取短信验证码信息 */
        SmsInfoMapper smsInfoMapper = new SmsInfoMapperImpl();
        SmsInfo smsInfo = smsInfoMapper.selectSmsInfoByPrimaryKey(instId, verifySeqNum);
        if (null == smsInfo) {
            rglog.error("短信验证码不存在,instId:<{}>,verifySeqNum:<{}>", instId, verifySeqNum);
            throw new BizException(OltpRpcdEnum.VERIFY_SMS_CODE_NO_EXIST_ERROR);
        }
        /*校验手机号是否一致,取可变长参数的第一条数据*/
        if (verifyPhoneNo.length > 0) {
            if (!verifyPhoneNo[0].equals(smsInfo.getVerifyMobileNo())) {
                rglog.error("校验短信验证码手机号不一致，验证:<{}>,申请:<{}>", smsInfo.getVerifyMobileNo(), verifyPhoneNo[0]);
                throw new BizException(OltpRpcdEnum.VERIFY_SMS_CODE_PHONE_ERROR);
            }
        }
        /* Step2：校验短信验证码 */
        /* Step2.1：校验短信验证码类型是否一致 */
        String verifyReultDesc = "";
        OltpRpcdEnum oltpRpcdEnum = OltpRpcdEnum.VERIFY_SMS_CODE_ERROR;
        boolean verifyResult = smsType.equals(smsInfo.getSmsType());
        if (!verifyResult) {
            rglog.error("短信验证码类型不一致,验证:<{}>,申请:<{}>", smsType, smsInfo.getSmsType());
            oltpRpcdEnum = OltpRpcdEnum.VERIFY_SMS_CODE_TYPE_ERROR;
        } else {
            /* Step2.2：校验短信验证码是否一致 */
            verifyResult = smsCode.equals(smsInfo.getSmsCode());
            if (!verifyResult) {
                rglog.error("短信验证码不一致,验证:<{}>,申请:<{}>", smsCode, smsInfo.getSmsCode());
                oltpRpcdEnum = OltpRpcdEnum.VERIFY_SMS_CODE_NOT_MACHTED_ERROR;
            } else {
                /* Step2.3：校验短信验证码是否过期 */
                String currentDateTime = DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS");
                verifyResult = DateUtil.compareTime(smsInfo.getSmsExpiryTime(), currentDateTime, "yyyy-MM-dd HH:mm:ss.SSS") > 0;
                if (!verifyResult) {
                    rglog.error("短信验证码已过期,当前时间:<{}>,过期时间:<{}>", currentDateTime, smsInfo.getSmsExpiryTime());
                    oltpRpcdEnum = OltpRpcdEnum.VERIFY_SMS_CODE_EXPIRY_ERROR;
                } else {
                    /* Step2.4：校验短信验证码类型是否超过最大验证次数 */
                    verifyResult = (smsInfo.getTryTimes() + 1) <= smsInfo.getTryMaxTimes();
                    if (!verifyResult) {
                        rglog.error("短信验证码已超过验证次数:<{}>", smsInfo.getTryMaxTimes());
                        oltpRpcdEnum = OltpRpcdEnum.VERIFY_SMS_CODE_EXCEED_ERROR;
                    }
                }
            }
        }
        /* Step3：更新短信验证码状态 */
        if (verifyResult) {
            smsInfo.setSmsStatus(AppParamConstant.YES);
        }
        smsInfo.setRemark1(verifyReultDesc);
        smsInfo.setTryTimes(smsInfo.getTryTimes() + 1);
        int iReturnCode = smsInfoMapper.updateSmsInfoByPrimaryKey(smsInfo);
        if (0 != iReturnCode || !verifyResult) {
            throw new BizException(oltpRpcdEnum);
        }
        return true;
    }
}
